#include "splinecurve.h"
#include "qdebug.h"
#include "qmath.h"

#include <QEasingCurve>
#include <vector>



qreal distance2(const QPointF& pt1, const QPointF& pt2){
    QPointF pt = pt1 - pt2;
    return pt.x() * pt.x() + pt.y() * pt.y();
}

qreal distance(const QPointF& pt1, const QPointF& pt2){
    return qSqrt(distance2(pt1, pt2));
}

/**
 * @brief projection a点到直线bc上的投影
 * @param a
 * @param b
 * @param c
 * @return
 */
QPointF projection(const QPointF& a, const QPointF& b, const QPointF& c){
    QPointF bc = c - b;
    QPointF ba = a - b;

    qreal d = (bc.x() * ba.x() + bc.y() * ba.y()) / distance2(b, c);
    return d * bc + b;
}

/**
 * @brief sampleSection 线段采样
 * @param p1
 * @param p2
 * @param t 时间 0~1
 * @return t == 0 返回 p1  t == 1 返回 p2
 */

QPointF sampleSection(const QPointF& p1, const QPointF& p2, qreal t){
    return p1 + t * (p2 - p1);
}

/**
 * @brief sampleBeizer 二阶贝塞尔曲线采样
 * @param p1
 * @param p2
 * @param p3
 * @param t  时间 0~1
 * @return
 */
QPointF sampleBeizer(const QPointF& p1, const QPointF& p2, const QPointF& p3, qreal t){
    QPointF res = sampleSection(sampleSection(p1, p2, t), sampleSection(p2, p3, t), t);
    res.setX(std::max(res.x(), p1.x()));
    res.setX(std::min(res.x(), p3.x()));
    return res;
}

SplineCurve::SplineCurve(QString name):m_name(name) {

    m_color = QColor::fromHsl(rand() % 360,rand() % 256,rand() % 200);
    addPoint(QPointF(0, 0));
    addPoint(QPointF(1, 1));
}

SplinePoint::ptr SplineCurve::addPoint(QPointF point, SplinePoint::Select select)
{
    auto it = m_points.find(point.x());
    if(it != m_points.end())
        return it->second;

    SplinePoint::ptr sp(new SplinePoint(point, select));
    m_points.emplace(point.x(), sp);

    it = m_points.lower_bound(point.x());
    if(it != m_points.begin()){
        sp->setPrev(std::prev(it)->second);
    }
    if(next(it) != m_points.end()){
        sp->setNext(std::next(it)->second);
    }

    if(select != SplinePoint::Select::_None)
        m_selectPoints.emplace(sp);
    return sp;
}

void SplineCurve::deletePoint()
{
    for(auto& sp:m_selectPoints){
        m_points.erase(sp->getPoint().x());
    }
    m_selectPoints.clear();
}

void SplineCurve::setTangent(TangentMode tangent)
{
    for(auto it = m_points.begin(); it != m_points.end(); ++ it){

        if(it->second->isSelect()){

            it->second->setTangentMode(tangent);
        }


    }
}

void SplineCurve::visitAllPoints(std::function<void(const SplinePoint::ptr&)> callback)
{
    for(auto &&[_, spline]:m_points){
        callback(spline);
    }
}

void SplineCurve::visitSelectedPoints(std::function<void (QPointF &)> callback)
{
    for(auto& sp:m_selectPoints){
        switch (sp->getSelect()) {
        case SplinePoint::_Point:
            callback(sp->getPoint());
            break;
        case SplinePoint::_Handle1:
            callback(sp->getHandle1());
            break;
        case SplinePoint::_Handle2:
            callback(sp->getHandle2());
            break;
        default:
            break;
        }
    }
}

void SplineCurve::setSelectPointsPosition(QPointF position)
{
    for(auto& sp:m_selectPoints){
        if(sp->isSelect()){
            QPointF offset = position - sp->getPoint();
            if(sp->getSelect() == SplinePoint::_Handle1){
                offset = position - sp->getHandle1();
            }else if(sp->getSelect() == SplinePoint::_Handle2){
                offset = position - sp->getHandle2();
            }
            movePoint(sp, offset);
        }
    }
}

void SplineCurve::selectPoints(const QRectF &rect, bool isAdded)
{
    if(!isAdded){
        m_selectPoints.clear();
    }

    for(auto &&[_, sp]:m_points){
        SplinePoint::Select s = sp->check(rect);
        if(s == SplinePoint::_None){
            if(!isAdded){
               sp->setSelect(s);
            }
        }else{
            sp->setSelect(s);
            m_selectPoints.emplace(sp);
        }
    }
}

void SplineCurve::clearSelectPoints()
{
    for(auto &sp : m_selectPoints){
        sp->setSelect(SplinePoint::Select::_None);
    }
    m_selectPoints.clear();
}

void SplineCurve::moveSelectPoints(const QPointF offset)
{
    for(auto& sp:m_selectPoints){
        movePoint(sp, offset);
    }
}

std::vector<QPointF> SplineCurve::getCurve(qreal left, qreal right)
{
    std::vector<QPointF> curve;
    for(auto it = m_points.begin(); it != m_points.end(); ++ it){
        auto sp = it->second;
        QPointF point = sp->getPoint();

        if(sp->getPrev() && sp->getPrev()->getTangentMode() == TangentMode::Constant){
            curve.emplace_back(point.x(), sp->getPrev()->getPoint().y());
        }

        if(point.x() <= left){
            if(!curve.empty())curve.pop_back();
            curve.emplace_back(point);
        }else if(point.x() >= right){
            curve.emplace_back(point);
            break;
        }else if(curve.empty()){
            curve.emplace_back(left, point.y());
        }



        switch (sp->getTangentMode()) {
        case TangentMode::Linear:
        case TangentMode::Constant:{
            curve.emplace_back(point);
            break;
        }
        case TangentMode::User:
        case TangentMode::Auto:
        case TangentMode::Break:{

            if(sp->getPrev() && sp->getPrev()->getTangentMode() != TangentMode::Constant){
                QPointF pt = sp->getPrev()->getPoint();
                if(sp->getPrev()->hasHandle()){
                    pt = (sp->getPrev()->getHandle2() + sp->getHandle1()) / 2;
                }
                for(int t = 0; t <= 100; t += 2){
                    curve.emplace_back(sampleBeizer(pt, sp->getHandle1(), sp->getPoint(), t * 0.01));
                }

            }else{
                curve.emplace_back(point);
            }

            if(sp->getNext()){
                QPointF pt = sp->getNext()->getPoint();
                if(sp->getNext()->hasHandle()){
                    pt = (sp->getNext()->getHandle1() + sp->getHandle2()) / 2;
                }
                for(int t = 0; t <= 100; t += 2){
                    curve.emplace_back(sampleBeizer(sp->getPoint(), sp->getHandle2(), pt, t * 0.01));
                }

            }



            break;
        }

        default:
            break;
        }
    }

    if(!curve.empty() && curve.back().x() < right){
        curve.emplace_back(right, curve.back().y());
    }


    return curve;
}

bool SplineCurve::checkSelectPointTangentMode(TangentMode mode)
{
    //单选
    if(countSelectPoint() != 1)
        return false;
    return (*m_selectPoints.begin())->getTangentMode() == mode;
}

const QString &SplineCurve::name()
{
    return m_name;
}

QColor SplineCurve::color() const
{
    return m_color;
}

void SplineCurve::setColor(const QColor &newColor)
{
    m_color = newColor;
}

void SplineCurve::movePoint(const SplinePoint::ptr &sp, const QPointF &offset)
{
    m_points.erase(sp->getPoint().x());
    sp->movePoint(offset);
    m_points[sp->getPoint().x()] = sp;

    auto it = m_points.lower_bound(sp->getPoint().x());
    if(it != m_points.begin()){
        auto prev = std::prev(it);
        sp->setPrev(prev->second);
        if(prev->second->getTangentMode() == TangentMode::Auto){
            prev->second->autoHandle();
        }
    }
    if(next(it) != m_points.end()){
        auto next = std::next(it);
        sp->setNext(next->second);
        if(next->second->getTangentMode() == TangentMode::Auto){
            next->second->autoHandle();
        }
    }
}

static qint64 s_point_id = 0;

SplinePoint::SplinePoint():m_point(0, 0), m_handle1(0, 0), m_handle2(0, 0)
{
    m_id = s_point_id ++;
}

SplinePoint::SplinePoint(QPointF point, Select select):m_id(s_point_id ++), m_point(point), m_handle1(point), m_handle2(point), m_select(select)
{
}

void SplinePoint::setPosition(QPointF position)
{
    switch (m_tangentMode) {
    case TangentMode::Linear:
    case TangentMode::Constant:{
        m_point = position;
        break;
    }
    case TangentMode::User:
    case TangentMode::Auto:{
        if(m_select == Select::_Point){
            m_point = position;
            m_handle1 = position;
            m_handle2 = position;
        }else if(m_select == Select::_Handle1){
            m_handle1 = position;
            m_handle2 = (m_point - m_handle1) / distance(m_point, m_handle1) * distance(m_handle2, m_point) + m_point;

        }else if(m_select == Select::_Handle2){
            m_handle2 = position;
            m_handle1 = (m_point - m_handle2) / distance(m_point, m_handle2) * distance(m_handle1, m_point) + m_point;
        }


        break;
    }
    case TangentMode::Break:{

        if(m_select == Select::_Point){
            m_point = position;
            m_handle1 = position;
            m_handle2 = position;
        }else if(m_select == Select::_Handle1){
            m_handle1 = position;
        }else if(m_select == Select::_Handle2){
            m_handle2 = position;
        }

        break;
    }
    default:
        break;
    }
}

void SplinePoint::movePoint(QPointF offset)
{


    switch (m_tangentMode) {
    case TangentMode::Linear:
    case TangentMode::Constant:{
        m_point += offset;
        break;
    }
    case TangentMode::User:
    case TangentMode::Auto:{
        if(m_select == Select::_Point){
            m_point += offset;
            m_handle1 += offset;
            m_handle2 += offset;
        }else if(m_select == Select::_Handle1){
            m_handle1 += offset;
            m_handle2 = (m_point - m_handle1) / distance(m_point, m_handle1) * distance(m_handle2, m_point) + m_point;

        }else if(m_select == Select::_Handle2){
            m_handle2 += offset;
            m_handle1 = (m_point - m_handle2) / distance(m_point, m_handle2) * distance(m_handle1, m_point) + m_point;
        }


        break;
    }
    case TangentMode::Break:{

        if(m_select == Select::_Point){
            m_point += offset;
            m_handle1 += offset;
            m_handle2 += offset;
        }else if(m_select == Select::_Handle1){
            m_handle1 += offset;
        }else if(m_select == Select::_Handle2){
            m_handle2 += offset;
        }

        break;
    }
    default:
        break;
    }

}


void SplinePoint::setTangentMode(TangentMode mode)
{
    if(m_tangentMode == mode)
        return;
    m_tangentMode = mode;
    setSelect(Select::_Point);
    switch (m_tangentMode) {
    case TangentMode::Linear:
    case TangentMode::Constant:{
        break;
    }
    case TangentMode::User:
    case TangentMode::Auto:{

        autoHandle();

        break;
    }
    case TangentMode::Break:{

        if(!prev || !next){
            if(prev){
                m_handle1 = prev->m_point + 0.75 * (m_point - prev->m_point);
                m_handle2.setX(m_point.x() + 0.1);
                m_handle2.setY(m_point.y());
            }else if(next){
                m_handle2 = next->m_point + 0.75 * (m_point - next->m_point);
                m_handle1.setX(m_point.x() - 0.1);
                m_handle1.setY(m_point.y());
            }else{
                QPointF pt(0.1, 0.0);
                m_handle1 = m_point - pt;
                m_handle2 = m_point + pt;
            }
        }else{
            m_handle1 = prev->m_point + 0.75 * (m_point - prev->m_point);
            m_handle2 = next->m_point + 0.75 * (m_point - next->m_point);
        }

        break;
    }
    default:
        break;
    }


}

bool SplinePoint::hasHandle() const
{
    return getTangentMode() != TangentMode::Linear && getTangentMode() != TangentMode::Constant;
}

SplinePoint::Select SplinePoint::check(const QRectF &rect)
{
    if(rect.contains(m_point)){
        return Select::_Point;
    }

    if(hasHandle() && isSelect()){
        if(rect.contains(m_handle1)){
            return Select::_Handle1;
        }else if(rect.contains(m_handle2)){
            return Select::_Handle2;
        }
    }

    return Select::_None;
}

void SplinePoint::autoHandle()
{
    if(!prev || !next){
        QPointF pt(0.1, 0.0);
        if(prev){
            pt.setX(0.25 * (m_point.x() - prev->m_point.x()));
        }else if(next){
            pt.setX(0.25 * (next->m_point.x() - m_point.x()));
        }
        m_handle1 = m_point - pt;
        m_handle2 = m_point + pt;
    }else{
        QPointF offset = m_point - projection(m_point, prev->m_point, next->m_point);
        QPointF pt1 = prev->m_point + offset;
        QPointF pt2 = next->m_point + offset;

        m_handle1 = pt1 + 0.75 * (m_point - pt1);
        m_handle2 = pt2 + 0.75 * (m_point - pt2);
    }
}

void SplinePoint::setPrev(ptr sp)
{
    if(prev == sp)
        return;
    if(prev){
        prev->next = sp;
    }
    if(sp){
        sp->prev = prev;
        sp->next = shared_from_this();
    }
    prev = sp;

}

void SplinePoint::setNext(ptr sp)
{
    if(next == sp)
        return;
    if(next){
        next->prev = sp;
    }
    if(sp){
        sp->next = next;
        sp->prev = shared_from_this();
    }
    next = sp;
}

